﻿using System.Runtime.CompilerServices;

namespace BattleCore.FixedPoint
{
    /// <summary>
    /// Contains common math operations.
    /// </summary>
    public sealed class fpmath
    {
        /// <summary>
        /// PI constant.
        /// </summary>
        public static fp Pi = fp.Pi;

        /**
        *  @brief PI over 2 constant.
        **/
        public static fp PiOver2 = fp.PiOver2;

        /// <summary>
        /// A small value often used to decide if numeric 
        /// results are zero.
        /// </summary>
        public static fp Epsilon = fp.Epsilon;

        /**
        *  @brief Degree to radians constant.
        **/
        public static fp Deg2Rad = fp.Deg2Rad;

        /**
        *  @brief Radians to degree constant.
        **/
        public static fp Rad2Deg = fp.Rad2Deg;

        /// <summary>
        /// Gets the square root.
        /// </summary>
        /// <param name="number">The number to get the square root from.</param>
        /// <returns></returns>

        #region public static FP Sqrt(FP number)

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static fp Sqrt(fp number)
        {
            return fp.Sqrt(number);
        }

        #endregion

        /// <summary>
        /// Gets the maximum number of two values.
        /// </summary>
        /// <param name="val1">The first value.</param>
        /// <param name="val2">The second value.</param>
        /// <returns>Returns the largest value.</returns>

        #region public static FP Max(FP val1, FP val2)

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static fp Max(fp val1, fp val2)
        {
            return (val1 > val2) ? val1 : val2;
        }

        #endregion

        /// <summary>
        /// Gets the minimum number of two values.
        /// </summary>
        /// <param name="val1">The first value.</param>
        /// <param name="val2">The second value.</param>
        /// <returns>Returns the smallest value.</returns>

        #region public static FP Min(FP val1, FP val2)

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static fp Min(fp val1, fp val2)
        {
            return (val1 < val2) ? val1 : val2;
        }

        #endregion

        /// <summary>
        /// Gets the maximum number of three values.
        /// </summary>
        /// <param name="val1">The first value.</param>
        /// <param name="val2">The second value.</param>
        /// <param name="val3">The third value.</param>
        /// <returns>Returns the largest value.</returns>

        #region public static FP Max(FP val1, FP val2,FP val3)

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static fp Max(fp val1, fp val2, fp val3)
        {
            fp max12 = (val1 > val2) ? val1 : val2;
            return (max12 > val3) ? max12 : val3;
        }

        #endregion

        /// <summary>
        /// Returns a number which is within [min,max]
        /// </summary>
        /// <param name="value">The value to clamp.</param>
        /// <param name="min">The minimum value.</param>
        /// <param name="max">The maximum value.</param>
        /// <returns>The clamped value.</returns>

        #region public static FP Clamp(FP value, FP min, FP max)

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static fp Clamp(fp value, fp min, fp max)
        {
            value = (value > max) ? max : value;
            value = (value < min) ? min : value;
            return value;
        }

        #endregion

        /// <summary>
        /// Changes every sign of the matrix entry to '+'
        /// </summary>
        /// <param name="matrix">The matrix.</param>
        /// <param name="result">The absolute matrix.</param>

        #region public static void Absolute(ref JMatrix matrix,out JMatrix result)

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static void Absolute(ref fpmatrix matrix, out fpmatrix result)
        {
            result.M11 = fp.Abs(matrix.M11);
            result.M12 = fp.Abs(matrix.M12);
            result.M13 = fp.Abs(matrix.M13);
            result.M21 = fp.Abs(matrix.M21);
            result.M22 = fp.Abs(matrix.M22);
            result.M23 = fp.Abs(matrix.M23);
            result.M31 = fp.Abs(matrix.M31);
            result.M32 = fp.Abs(matrix.M32);
            result.M33 = fp.Abs(matrix.M33);
        }

        #endregion

        /// <summary>
        /// Returns the sine of value.
        /// </summary>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static fp Sin(fp value)
        {
            return fp.Sin(value);
        }

        /// <summary>
        /// Returns the cosine of value.
        /// </summary>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static fp Cos(fp value)
        {
            return fp.Cos(value);
        }

        /// <summary>
        /// Returns the tan of value.
        /// </summary>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static fp Tan(fp value)
        {
            return fp.Tan(value);
        }

        /// <summary>
        /// Returns the arc sine of value.
        /// </summary>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static fp Asin(fp value)
        {
            return fp.Asin(value);
        }

        /// <summary>
        /// Returns the arc cosine of value.
        /// </summary>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static fp Acos(fp value)
        {
            return fp.Acos(value);
        }

        /// <summary>
        /// Returns the arc tan of value.
        /// </summary>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static fp Atan(fp value)
        {
            return fp.Atan(value);
        }

        /// <summary>
        /// Returns the arc tan of coordinates x-y.
        /// </summary>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static fp Atan2(fp y, fp x)
        {
            return fp.Atan2(y, x);
        }

        /// <summary>
        /// Returns the largest integer less than or equal to the specified number.
        /// </summary>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static fp Floor(fp value)
        {
            return fp.Floor(value);
        }

        /// <summary>
        /// Returns the smallest integral value that is greater than or equal to the specified number.
        /// </summary>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static fp Ceiling(fp value)
        {
            return value;
        }

        /// <summary>
        /// Rounds a value to the nearest integral value.
        /// If the value is halfway between an even and an uneven value, returns the even value.
        /// </summary>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static fp Round(fp value)
        {
            return fp.Round(value);
        }

        /// <summary>
        /// Returns a number indicating the sign of a Fix64 number.
        /// Returns 1 if the value is positive, 0 if is 0, and -1 if it is negative.
        /// </summary>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static int Sign(fp value)
        {
            return fp.Sign(value);
        }

        /// <summary>
        /// Returns the absolute value of a Fix64 number.
        /// Note: Abs(Fix64.MinValue) == Fix64.MaxValue.
        /// </summary>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static fp Abs(fp value)
        {
            return fp.Abs(value);
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static fp Barycentric(fp value1, fp value2, fp value3,
            fp amount1, fp amount2)
        {
            return value1 + (value2 - value1) * amount1 + (value3 - value1) * amount2;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static fp CatmullRom(fp value1, fp value2, fp value3,
            fp value4, fp amount)
        {
            // Using formula from http://www.mvps.org/directx/articles/catmull/
            // Internally using FPs not to lose precission
            fp amountSquared = amount * amount;
            fp amountCubed = amountSquared * amount;
            return (fp)(0.5 * (2.0 * value2 +
                               (value3 - value1) * amount +
                               (2.0 * value1 - 5.0 * value2 + 4.0 * value3 - value4) * amountSquared +
                               (3.0 * value2 - value1 - 3.0 * value3 + value4) * amountCubed));
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static fp Distance(fp value1, fp value2)
        {
            return fp.Abs(value1 - value2);
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static fp Hermite(fp value1, fp tangent1, fp value2,
            fp tangent2, fp amount)
        {
            // All transformed to FP not to lose precission
            // Otherwise, for high numbers of param:amount the result is NaN instead of Infinity
            fp v1 = value1, v2 = value2, t1 = tangent1, t2 = tangent2, s = amount, result;
            fp sCubed = s * s * s;
            fp sSquared = s * s;

            if (amount == 0f)
                result = value1;
            else if (amount == 1f)
                result = value2;
            else
                result = (2 * v1 - 2 * v2 + t2 + t1) * sCubed +
                         (3 * v2 - 3 * v1 - 2 * t1 - t2) * sSquared +
                         t1 * s +
                         v1;
            return (fp)result;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static fp Lerp(fp value1, fp value2, fp amount)
        {
            return value1 + (value2 - value1) * amount;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static fp InverseLerp(fp value1, fp value2, fp amount)
        {
            return (amount - value1) / (value2 - value1);
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static fp SmoothStep(fp value1, fp value2, fp amount)
        {
            // It is expected that 0 < amount < 1
            // If amount < 0, return value1
            // If amount > 1, return value2
            fp result = Clamp(amount, 0f, 1f);
            result = Hermite(value1, 0f, value2, 0f, result);
            return result;
        }
    }
}